﻿using System.Web.Security;
using System;
using System.Text;
using System.Linq;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Runtime.CompilerServices;
using System.Collections.Specialized;
using ProjetoComperio.Framework.Web.Mail;

[assembly: InternalsVisibleTo("Test")]
namespace ProjetoComperio.Framework.Web.Security
{
    /// <summary>
    /// This class Provides the Membership Controls to Comperio
    /// </summary>
    public class ComperioMemberShipProvider : MembershipProvider
    {
        public class Roles
        {
            public const string Student = "Student";
            public const string AcademicCoordinator = "AcademicCoordinator";
            public const string Visitor = "Visitor";


        }
        #region enum
        private enum FailureType
        {
            Password = 1, PasswordAnswer
        }
        #endregion

        #region Fields
        [Obsolete("This field isn't used")]
        string _applicationName;

        ComperioFramework.Web.Security.ComperioEntities _dbContext;
        #endregion

        #region properties
        public String ProviderName
        {
            // 
            get { return "AspNetSqlMembershipProvider"; }
        }
        ComperioFramework.Web.Security.ComperioEntities DbContext
        {

            get
            {
                if (_dbContext == null)
                    _dbContext = new ComperioEntities();
                return _dbContext;
            }
        }
        public override string ApplicationName
        {
            get { return "Comperio"; }

            set { _applicationName = value; }

        }
        public override bool EnablePasswordReset
        {
            get { return false; }

        }
        public override bool EnablePasswordRetrieval
        {
            get { return true; }

        }
        public override int MaxInvalidPasswordAttempts
        {
            get { return 5; }

        }
        public override int MinRequiredNonAlphanumericCharacters
        {
            get { return 3; }

        }
        public override int MinRequiredPasswordLength
        {
            get { return 5; }

        }
        public override int PasswordAttemptWindow
        {
            get { return 5; }

        }
        public override MembershipPasswordFormat PasswordFormat
        {
            get { return MembershipPasswordFormat.Encrypted; }

        }

        public override string PasswordStrengthRegularExpression
        {
            get { return String.Empty; }

        }
        public override bool RequiresQuestionAndAnswer
        {
            get { return true; }

        }
        public override bool RequiresUniqueEmail
        {
            get { return true; }

        }

        #endregion

        #region methods
        public ComperioMemberShipProvider()
        {
        }

        private string EncodePassword(string password)
        {


            MD5 md5 = new MD5CryptoServiceProvider();

            Byte[] encodedBytes = EncryptPassword(ASCIIEncoding.Default.GetBytes(password));


            // Bytes to string
            if (encodedBytes == null)
                throw new ArgumentException();

            return System.Text.RegularExpressions.Regex.Replace(BitConverter.ToString(encodedBytes), "-", "").ToLower();

        }

        public override bool ChangePassword(string username, string oldPassword, string newPassword)
        {

            return false;

        }

        public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
        {
            return false;
        }

        private Boolean ValidatePassword(String username, String password)
        {


            MembershipUser user = this.RetrieveMembershipUserByUserName(username);
            if (user != null && user.GetPassword().Equals(this.EncodePassword(password)))
                return true;
            else
                return false;


        }

        /// <summary>
        /// This method validates user Data
        /// </summary>
        /// <param name="userName"></param>
        /// <param name="password"></param>
        /// <param name="status"></param>
        /// <returns></returns>
        public bool CheckValidUsernameAndPassword(String userName, String password, string email, out MembershipCreateStatus status)
        {
            status = MembershipCreateStatus.Success;

            //Password Validation
            ValidatePasswordEventArgs args = new ValidatePasswordEventArgs(userName, password, true);
            OnValidatingPassword(args);
            if (args.Cancel)
            {
                status = MembershipCreateStatus.InvalidPassword;
                return false;
            }

            //Email Validation
            if (RequiresUniqueEmail && !String.IsNullOrEmpty(GetUserNameByEmail(email)))
            {
                status = MembershipCreateStatus.DuplicateEmail;
                return false;
            }

            return true;
        }


        /// <summary>
        /// A razão deste método existir é passar um objeto por parametro, pois os parametros podem crescer ao longo do tempo
        /// esta função gera um novo usuário: 
        ///preenchendo as informações default;
        ///recebendo as informações digitadas pelo usuário;
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        public MembershipUser CreateUser(User user)
        {
            MembershipCreateStatus status;

            if (!CheckValidUsernameAndPassword(user.UserName, user.Password, user.Email, out status))
                return null;

            user.Password = this.EncodePassword(user.Password);

            user.IsApproved = true;
            user.LastActivityDate = DateTime.Now;
            user.LastLoginDate = DateTime.Now;
            user.LastPasswordChangedDate = DateTime.Now;
            user.CreationDate = DateTime.Now;
            user.IsLockedOut = true;
            user.IsOnLine = false;
            user.LastLockedOutDate = DateTime.Now;
            user.FailedPasswordAnswerAttemptCount = 0;
            user.FailedPasswordAttemptWindowStart = DateTime.Now;
            user.FailedPasswordAttemptCount = 0;
            user.Role = RetrieveRole(user.Role.Name);
            user.FailedPasswordAnswerAttemptWindowStart = DateTime.Now;
            //user.ApplicationName = this.ApplicationName;
            user.Comment = String.Empty;

            user.PasswordQuestion = "Default";
            user.PasswordAnswer = "Default";

            DbContext.AddToUser(user);
            DbContext.SaveChanges();
            SendNotificationToComperioAdmin(user);
            return ConvertUserToMembershipUser(user);
        }

        public override MembershipUser CreateUser(string userName, string password, string email, string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, out MembershipCreateStatus status)
        {

            status = MembershipCreateStatus.Success;

            User user = new User();
            isApproved = true;

            if (!CheckValidUsernameAndPassword(userName, password, email, out status))
                return null;

            status = MembershipCreateStatus.Success;

            user.UserName = userName;

            user.Password = this.EncodePassword(password);
            user.Email = email;
            user.PasswordQuestion = passwordQuestion;
            user.PasswordAnswer = this.EncodePassword(passwordAnswer);

            user.IsApproved = isApproved;
            user.LastActivityDate = DateTime.Now;
            user.LastLoginDate = DateTime.Now;
            user.LastPasswordChangedDate = DateTime.Now;
            user.CreationDate = DateTime.Now;
            user.IsLockedOut = true;
            user.IsOnLine = false;
            user.LastLockedOutDate = DateTime.Now;
            user.FailedPasswordAnswerAttemptCount = 0;
            user.FailedPasswordAttemptWindowStart = DateTime.Now;
            user.FailedPasswordAttemptCount = 0;
            user.FailedPasswordAnswerAttemptWindowStart = DateTime.Now;
            //user.ApplicationName = this.ApplicationName;
            user.Comment = String.Empty;
            user.Role = null;

            this.DbContext.AddToUser(user);
            this.DbContext.SaveChanges();

            status = MembershipCreateStatus.Success;
            SendNotificationToComperioAdmin(user);
            return ConvertUserToMembershipUser(user);
        }

        protected override byte[] DecryptPassword(byte[] encodedPassword)
        {
            return encodedPassword;

        }

        public override bool DeleteUser(string username, bool deleteAllRelatedData)
        {
            return false;

        }

        protected override byte[] EncryptPassword(byte[] password)
        {
            return new MD5CryptoServiceProvider().ComputeHash(password);
        }

        public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            //buscar os usuários de acordo com os requisitos
            // converter os usuários do banco p MembershipUser
            // Criar um MembershipUserCollection e adicionar todos os usuários
            //totalRecords = 0;
            //return new MembershipUserCollection();


            IQueryable<User> users = DbContext.User.Where(user => user.Email.Equals(emailToMatch));
            totalRecords = users.Count();

#warning     implementar sortAndPage
            users = users.Skip(pageSize * pageIndex).Take(pageSize);

            MembershipUserCollection membershipUserCollection = new MembershipUserCollection();
            foreach (User user in users)
                membershipUserCollection.Add(this.ConvertUserToMembershipUser(user));


            return membershipUserCollection;
        }

        public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 1;
            return new MembershipUserCollection();
        }

        public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
        {
            totalRecords = 1;
            return new MembershipUserCollection();
        }

        public override int GetNumberOfUsersOnline()
        {
            return 1;

        }

        public override string GetPassword(string username, string answer)
        {
            return "teste";


        }

        public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
        {

            return null;
        }

        public override MembershipUser GetUser(string username, bool userIsOnline)
        {


            User user = DbContext.User.Where(u => u.UserName.Equals(username) && u.IsOnLine == userIsOnline).FirstOrDefault();

            if (user == default(User))
                return null;

            return ConvertUserToMembershipUser(user);




        }

        public override string GetUserNameByEmail(string email)
        {
            User user = DbContext.User.Where(u => u.Email.Equals(email)).FirstOrDefault();
            return user == null ? String.Empty : user.UserName;


        }

        protected override void OnValidatingPassword(ValidatePasswordEventArgs e)
        {
            e.Cancel = true;
            if (e.Password.Length < 4)
                return;
            if (e.Password.Contains(e.UserName))
                return;


            e.Cancel = false;



        }

        public override string ResetPassword(string username, string answer)
        {
            return "teste";
        }

        public override bool UnlockUser(string userName)
        {
            return false;
        }

        public override void UpdateUser(MembershipUser user)
        {


        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userName">must be not null</param>
        /// <param name="password">must be not null</param>
        /// <returns></returns>
        public override bool ValidateUser(string userName, string password)
        {

            Boolean isValid = false;

            //
            if (String.IsNullOrEmpty(userName) || String.IsNullOrEmpty(password))
                return false;

            User user = this.RetrieveActiveUser(userName);
            if (user == default(User))
                return false;


            if (CheckPassword(password, user.Password))
            {
                if (user.IsApproved)
                {
                    isValid = true;
                    user.LastLoginDate = DateTime.Now;
                    DbContext.SaveChanges();
                }
            }
            else
                UpdateFailureCount(userName, FailureType.Password);

            return isValid;
        }

        private void UpdateFailureCount(string userName, FailureType failureType)
        {
            User user;

            if (!String.IsNullOrEmpty(userName))
                user = this.RetrieveActiveUser(userName);
            else
                return;

            DateTime windowStart = DateTime.MinValue;
            Int32 failureCount = 0;


            switch (failureType)
            {
                case FailureType.Password:
                    {
                        failureCount = (Int32)user.FailedPasswordAttemptCount;
                        windowStart = (DateTime)user.FailedPasswordAnswerAttemptWindowStart;
                        break;
                    }
                case FailureType.PasswordAnswer:
                    {
                        failureCount = (Int32)user.FailedPasswordAnswerAttemptCount;
                        windowStart = (DateTime)user.FailedPasswordAnswerAttemptWindowStart;
                        break;
                    }
                default:
                    {
                        Debug.Assert(true, "InvalidFailureType");
                        break;
                    }

            }

            DateTime windowEnd = windowStart.AddMinutes(this.PasswordAttemptWindow);

            if (failureCount == 0 || DateTime.Now > windowEnd)
            {
                switch (failureType)
                {
                    case FailureType.Password:
                        {
                            user.FailedPasswordAttemptCount = 1;
                            user.FailedPasswordAttemptWindowStart = DateTime.Now;
                            break;
                        }
                    case FailureType.PasswordAnswer:
                        {
                            user.FailedPasswordAnswerAttemptCount = 1;
                            user.FailedPasswordAnswerAttemptWindowStart = DateTime.Now;
                            break;
                        }

                }
                try
                {
                    DbContext.SaveChanges();
                }
                catch (Exception)
                {

                    throw new InvalidOperationException("Unable to update failure count and window start.");

                }


                return;
            }
            if (++user.FailedPasswordAttemptCount > this.MaxInvalidPasswordAttempts)
            {
                user.IsLockedOut = true;
                user.LastLockedOutDate = DateTime.Now;

                try
                {
                    DbContext.SaveChanges();
                }
                catch (Exception)
                {

                    throw new InvalidOperationException("Unable to lock out user.");
                }
            }

        }

        /// <summary>
        /// Method used to initialize the web.Config 
        /// </summary>
        /// <param name="name"></param>
        /// <param name="config"></param>
        public override void Initialize(string name, NameValueCollection config)
        {
            //
            // Initialize values from web.config.
            //

            if (config == null)
                throw new ArgumentNullException("config");

            if (String.IsNullOrEmpty(name))
                name = "ComperioMemberShipProvider";

            if (String.IsNullOrEmpty(config["ComperioMemberShipProvider"]))
            {
                config.Remove("description");
                config.Add("ComperioMemberShipProvider", "ComperioMemberShipProvider");
            }

            // Initialize the abstract base class.
            base.Initialize(name, config);



        }


        #endregion

        #region Customized Methods


        /// <summary>
        /// This method compares two passwords and return true if they are equals or false if they aren't
        /// </summary>
        /// <param name="password">must be not null</param>
        /// <param name="dbPassword">must be not null</param>
        /// <returns>valid boolean</returns>
        public bool CheckPassword(string password, string dbPassword)
        {



            if (!dbPassword.Equals(EncodePassword(password)))
                return false;


            return true;


        }

        /// <summary>
        /// This method converts an User to MembershipUser
        /// </summary>
        /// <param name="user">requires a not null user</param>
        /// <returns>a filled MembershipUser</returns>
        private MembershipUser ConvertUserToMembershipUser(User user)
        {

            MembershipUser membershipUser = new MembershipUser(this.ProviderName, user.UserName
                , null, user.Email, user.PasswordQuestion, user.Comment, user.IsApproved,
                user.IsLockedOut, user.CreationDate, user.LastLoginDate, user.LastActivityDate,
                user.LastPasswordChangedDate, user.LastLockedOutDate);

            return membershipUser;


        }

        /// <summary>
        /// This method returns an active(isn't locked out) user or a default entity
        /// </summary>
        /// <param name="userName">must be not null</param>
        /// <returns></returns>
        public User RetrieveActiveUser(String userName)
        {
            return DbContext.User.Where(u => u.UserName == userName && u.IsLockedOut == false).FirstOrDefault();

        }

        /// <summary>
        /// This method retrieves an User by its name
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public User RetrieveUser(String userName)
        {
            return DbContext.User.Where(u => u.UserName == userName).FirstOrDefault();

        }

        /// <summary>
        /// This method retrieves the user's role
        /// </summary>
        /// <param name="userName"></param>
        /// <returns></returns>
        public Role RetrieveUserRole(String userName)
        {

            var resul = from user in DbContext.User
                        where (user.UserName == userName && !user.IsLockedOut)
                        join role in DbContext.Role on user.Role.RoleID equals role.RoleID
                        select role;
            return resul.FirstOrDefault();

        }

        /// <summary>
        /// This methods returns an user if this user exists in otherwise null
        /// </summary>
        /// <param name="userName">a not null username</param>
        /// <returns>an user or a null object</returns>
        private MembershipUser RetrieveMembershipUserByUserName(String userName)
        {
            if (!String.IsNullOrEmpty(userName))
                return null;

            User user = RetrieveActiveUser(userName);
            if (user == null || user == default(User) || String.IsNullOrEmpty(userName))
                return null;

            return ConvertUserToMembershipUser(RetrieveActiveUser(userName));
        }

        /// <summary>
        /// This method sends a notification to comperio admin
        /// </summary>
        /// <param name="user"></param>
        private void SendNotificationToComperioAdmin(User user)
        {

            String message = @"O usuário " + user.UserName + @" se efetuou um cadastro as:" + DateTime.Now.ToString();
            MailSender.SendMail("suporte@projetocomperio.org", "Novo Cadastro", message);
        }

        #endregion

        #region Controller, Action Methods

        /// <summary>
        /// This method retrieves an Action By its Name
        /// </summary>
        /// <param name="controllerName"></param>
        /// <param name="actionName"></param>
        /// <returns></returns>
        public System.Action RetrieveActionByDescription(String controllerName, String actionName)
        {
            var query = from c in DbContext.Controller
                        where c.Name == controllerName
                        join act in DbContext.Action on c.ControllerID equals act.Controller.ControllerID
                        where act.Name == actionName
                        select act;
            return query.FirstOrDefault();
        }

        /// <summary>
        /// This method returns all controllers
        /// </summary>
        /// <returns></returns>
        public IQueryable<Controller> RetrieveControllers()
        {
            return DbContext.Controller;
        }

        /// <summary>
        /// This method retrieves a controller by its name
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public Controller RetrieveController(String name)
        {
            return RetrieveControllers().FirstOrDefault(x => x.Name == name);
        }

        /// <summary>
        /// This method retrieves Roleactions by RoleID
        /// </summary>
        /// <param name="roleID"></param>
        /// <returns></returns>
        public IQueryable<System.Action> RetrieveActionsByRole(Role role)
        {
            var query = from act in DbContext.Action
                        join rAction in DbContext.RoleAction on act.ActionID equals rAction.Action.ActionID
                        join r in DbContext.Role on rAction.Role.RoleID equals r.RoleID
                        where r.RoleID == role.RoleID
                        select act;
            return query;
        }

        /// <summary>
        /// Insert a new Contoller
        /// </summary>
        /// <param name="controller"></param>
        public void CreateController(Controller controller)
        {
            DbContext.Controller.AddObject(controller);
            DbContext.SaveChanges();
        }

        #endregion

        #region RoleMethods

        /// <summary>
        /// This method returns all roles
        /// </summary>
        /// <returns></returns>
        public IQueryable<Role> RetrieveRoles()
        {
            return DbContext.Role;
        }

        /// <summary>
        /// This method Returns a role by its names
        /// </summary>
        /// <param name="roleName"></param>
        /// <returns></returns>
        public Role RetrieveRole(String roleName)
        {
            return DbContext.Role.FirstOrDefault(r => r.Name == roleName);

        }

        /// <summary>
        /// Creates a new Role
        /// </summary>
        /// <param name="testRole"></param>
        public void CreateRole(Role role)
        {
            DbContext.Role.AddObject(role);
            DbContext.SaveChanges();
        }

        #endregion

        #region UserHasPermissions

        /// <summary>
        /// This method returns true if user has permission
        /// </summary>
        /// <param name="controllerName"></param>
        /// <param name="actionName"></param>
        /// <param name="identity"></param>
        /// <returns></returns>
        public bool UserHasPermission(String controllerName, String actionName, String userName)
        {
            if (String.IsNullOrEmpty(userName))
                return false;
            var action = RetrieveActionByDescription(controllerName, actionName);
            if (action == null)
                return false;
            return RetrieveActionsByRole(RetrieveUserRole(userName)).Any(a => a.ActionID == action.ActionID);

        }

        #endregion


        internal void CreateAction(System.Action action)
        {
            DbContext.Action.AddObject(action);
            DbContext.SaveChanges();
        }
    }
}
